OpenFGAのモデリングガイドを読み解く(前編)
OpenFGAは、ファイングレインド・アクセス・コントロール(Fine-Grained Access Control、FGAC)を実現するためのオープンソースの認可システムです。このシステムは、複雑なアクセス制御ポリシーを柔軟にモデリングし、アプリケーションやサービスに統合することを可能にします。
Fine-Grained Access Controlについてはこちらの記事がわかりやすかったので、参考にしてみてください。
さて今回の記事は、OpenFGA公式ドキュメントのモデリングガイドを読み解いていきます。
元のドキュメントは英語で書かれており日本語に翻訳してみたのですが、インラインコードまで翻訳してしまい、認可の構造を理解する上で重要な部分がわかりづらくなってしまいました。
そこで、この記事では英語の情報を日本語でシンプルにまとめつつ、参考コードを多めでお送りします。
では、はじめます。
モデリングのイントロダクション
一般的な権限チェックは
「ユーザーUは、オブジェクトOに対してアクションAを実行できますか」
ですがOpenFGAでは
「ユーザーUは、オブジェクトOと関係Rを持っていますか」
という考え方をします。
これをリレーションシップ・ベースド・アクセス・コントロール(Relationship-Based Access Control、ReBAC)と呼びます。
さて、公式ドキュメントではイントロダクションの次は以下のように続きます。
A Process For Defining Authorization Models
Defining an authorization model requires codifying an answer to the question "why could user U perform an action A on an object O?" for all use cases or actions in your system. This is an iterative process. For the purpose of this guide, we'll go through one iteration of this process using a simplified Google Drive like system as an example.
簡易的なGoogleドライブの認可を例に、モデリングをステップバイステップでやってみようということですが
雰囲気が掴めてないうちは理解が中々難しいので、完成版の簡易的なGoogleドライブの認可モデルと関係タプルだけ、さっと眺めるにとどめます。
簡易的なGoogleドライブの認可モデル(完成版)
model schema 1.1 type user type organization relations define member: [user, organization#member] type document relations define owner: [user, organization#member] define editor: [user, organization#member] define viewer: [user, organization#member] define parent: [folder] define can_share: owner or editor or owner from parent define can_view: viewer or editor or owner or viewer from parent or editor from parent or owner from parent define can_write: editor or owner or owner from parent define can_change_owner: owner
簡易的なGoogleドライブの関係タプル(完成版)
# アンは contoso 組織のメンバーです { user:"user:anne", relation: "member", object: "organization:contoso"} # ベスは fabrikam 組織のメンバーです { user:"user:beth", relation: "member", object: "organization:fabrikam"} # アンはドキュメント:1 を作成し、その所有者になります。 { user:"user:anne", relation: "owner", object: "document:1"} # アンは、編集者として fabrikam 組織のすべてのメンバーとドキュメント:1 を共有します。 { user:"organization:fabrikam#member", relation: "editor", object: "document:1"} # Beth が文書:2 を作成し、その所有者になります。 { user:"user:beth", relation: "owner", object: "document:2"} # Beth は、contoso 組織のすべてのメンバーと閲覧者として document:2 を共有します。 { user:"organization:contoso#member", relation: "viewer", object: "document:2"}
それらしい単語が並んでいますが、最初はよくわからないと思います。
私も、このGoogleドライブのモデリングと完成版モデルを最初に見たとき、「直感的にわかるような気がするけれど、書き方に規則性を感じない…」と混乱しました。
例えば、モデルには owner
と書いてある部分がありますが、同じ部分に can_share
と書かれていたりします。一方はユーザーの性質を表す単語なのに、一方はユーザーの権限を表す単語です。
これは、オブジェクト同士の「関係」を定義する上での柔軟性だろうことは理解できましたが、逆に規則性は見えませんでした。
でも安心してください、本記事を最後までご覧いただければ理解できるようになると思います!
直接関係を定義する
まず、もっとも基礎的なアクセス付与から学びます。
例えば「ユーザー Jane
はオブジェクト プロジェクト Sandcastle
と関係 View
を持つことができる」のモデルは以下のように表現できます。
model schema 1.1 type user type project relations define can_view: [user]
このモデルの関係タプルはこのようになります。
# ユーザー `Jane` はオブジェクト `プロジェクト Sandcastle` と関係 `can_view` を持っている { user: "Jane", relation: "can_view", object: "project:Sandcastle"}
このようにオブジェクトへのアクセスをユーザーに直接付与する方法を Direct Access
と呼びます。
ちなみに、モデルの読み方の個人的コツは
主語に来ることができるのは type
や define
で定義されている 名詞 です。
このモデルは、最も基本的なモデルですが、ユーザーひとりひとりに対して個別にアクセスを与えるようなケースでしか使えません。
# ユーザー `Jane` はオブジェクト `プロジェクト Sandcastle` と関係 `can_view` を持っている { user: "Jane", relation: "can_view", object: "project:Sandcastle"} # ユーザー `Bob` はオブジェクト `プロジェクト Watermemory` と関係 `can_view` を持っている { user: "Bob", relation: "can_view", object: "project:Watermemory"} ...
間接的に関係を定義する
ユーザーひとりひとりではなく組織の認可を考えるとき、多くの場合、ユーザーの役割に紐づくアクセス制御を実装します。
先ほどのモデルにロールを追加してみましょう。
model schema 1.1 type user type project relations define owner: [user] // ロール追加 define editor: [user] define viewer: [user] define can_edit: owner or editor // ロール対応するアクセス追加 define can_view: viewer define can_delete: owner
関係タプルはこのようになります。
# ユーザー `Jane` はオブジェクト `プロジェクト Sandcastle` と関係 `owner` を持っている { user: "Jane", relation: "owner", object: "project:Sandcastle"} # ユーザー `Bob` はオブジェクト `プロジェクト Sandcastle` と関係 `editor` を持っている { user: "Bob", relation: "editor", object: "project:Sandcastle"} # ユーザー `Sara` はオブジェクト `プロジェクト Sandcastle` と関係 `viewer` を持っている { user: "Sara", relation: "viewer", object: "project:Sandcastle"} ...
ユーザーに直接アクセスを付与していませんが、ロールを介して間接的にユーザーにはアクセスが付与されています。
プレイグラウンドで動作確認を行うことができます。
モデルと関係タプルを設定した後、以下のようなクエリを流すことでアクセス制御を視覚的に確認することができます。
# Jane はプロジェクト Sandcastle に対して can_edit の関係を持っていますか? is user:Jane related to project:Sandcastle as can_edit?
このように結果が表示されます。 can_edit
は Jane
だけでなく Bob
も実行できることが分かります。
では Viewer
である Sara
はどうでしょうか?
Sara
は can_edit
の関係にないことが表示されました。
このようにアクセス制御を間接的に解決するプロセスを chain of resolution
と呼んでいます。
これはロール・ベースド・アクセス・コントロール(Role-Based Access Control, RBAC)と呼ばれているものと似ている考え方です。
OpenFGAではこのようなモデルを定義することも可能なのですが、OpenFGAの本当の強みである「きめ細やかなアクセス制御」はここからです。
(注)ここでは分かりやすく従来のロール・ベースド・アクセス・コントロールと同じ考え方で説明していますが、OpenFGAでは、正確には「関係」を示しているに過ぎません。
ユーザーグループを定義する
OpenFGAは、任意のユーザーグループに対してアクセス制御することができます。先ほどのロールはシステム稼働前に開発者によってあらかじめ作成しますが、ユーザーグループはシステム稼働中にユーザーによって新しく作成されます。
例えば、GoogleドライブのグループやSlackのユーザーグループ、X(旧Twitter)のフォロワーもユーザーグループだと言うことができます。
では、そのモデルを見ていきましょう。
model schema 1.1 type user type project relations define owner: [user, group#member] // 主語にユーザーグループを追加 define editor: [user, group#member] define viewer: [user, group#member] define can_edit: owner or editor define can_view: viewer define can_delete: owner type group // オブジェクトにユーザーグループを追加 relations define member: [user, group#member]
関係タプルは以下のようになります。
# ユーザー `Jane` はオブジェクト `Ancientグループ` と関係 `member` を持っている { user: "Jane", relation: "member", object: "group:Ancient"} # ユーザー `Bob` はオブジェクト `Futureグループ` と関係 `member` を持っている { user: "Bob", relation: "member", object: "group:Future"} # ユーザー `Ancientグループのメンバー` はオブジェクト `Scarletプロジェクト` と関係 `owner` を持っている { user: "group:Ancient#member", relation: "owner", object: "project:Scarlet"} # ユーザー `Futureグループのメンバー` はオブジェクト `Violetプロジェクト` と関係 `owner` を持っている { user: "group:Future#member", relation: "owner", object: "project:Violet"}
Jane
は Ancient
のメンバーです。 Ancient
のメンバーは プロジェクト Scarlet
に参加しています。
オブジェクトの階層関係を定義する
OpenFGAは、オブジェクト間の関係を定義できます。そうすることで、ユーザーと1つのオブジェクトの関係が別のオブジェクトとの関係に影響を与える可能性があることを示すことができます。
例えば、Googleドライブのドキュメントとフォルダの関係がそうです。そのモデルと関係タプルを見ていきます。
model schema 1.1 type user type document relations define parent: [folder] define editor: [user] define viewer: [user] define can_edit: editor or editor from parent define can_view: viewer or editor or editor from parent type folder relations define parent: [folder] define editor: [user] or editor from parent
Scarlet > Ancient > Kodai
という階層関係にします。
Jane
は、 Scarlet
フォルダに対して editor
の関係にあります。
# ユーザー `Jane` はオブジェクト `Scarletフォルダ` と関係 `editor` を持っている { user: "Jane", relation: "editor", object: "folder:Scarlet"} # ユーザー `Scarletフォルダ` はオブジェクト `Ancientフォルダ` と関係 `parent` を持っている { user: "folder:Scarlet", relation: "parent", object: "folder:Ancient"} # ユーザー `Ancientフォルダ` はオブジェクト `Kodaiドキュメント` と関係 `parent` を持っている { user: "folder:Ancient", relation: "parent", object: "document:Kodai"}
chainした結果、 Jane
が Kodai
に対して editor
の関係にあることが確認できました。
ここまで、公式ドキュメントのモデリングガイドの
について説明が終わりました。ここまで来れば、冒頭の簡易版のGoogleドライブのモデルと関係タプルが読めるようになりグッと理解が進むと思います。
感想
OpenFGAがきめ細やかなアクセス制御ができると言われている理由が、ユーザーグループやオブジェクトの階層関係をモデルに落とし込める点にあると思います。
モデリングガイドはまだ残っていて
- Blocklists
- Public Access
- Multiple Restrictions
- Custom Roles
- Conditions
- …
は、本記事の後編として後日投稿します。
OpenFGAのモデリングについて、より深く理解したい方は、ぜひ公式ドキュメントをご覧になってください。
以上です。